package GP;

import java.util.ArrayList;
import java.util.Random;

import tree.Node;
import tree.Tree;

public class CrossOver {
	
	private Random random;
	final double crossOverRate = 0.9;
	
	public CrossOver(Random random) {
		this.random = random;
	}
	
	public Tree crossOver(Tree parent1, Tree parent2){
		Tree ans;
		if (crossOverRate < random.nextDouble()){
			//don't make cross over, return parent
			ans = parent1.cloneTree();
		}
		else{
			//make crossover
			ans = gpCrossOver(parent1,parent2);
		}
		return ans ;			
	}
	
	public Tree gpCrossOver(Tree parent1, Tree parent2){
		Node [] nodes;
		Tree child;
		if (random.nextDouble()>0.5){
			//pick first parent
			child=parent1.cloneTree();
			nodes = choseValidRandNodes(child,parent2);
		}else{
			//pick second parent
			child=parent2.cloneTree();
			nodes = choseValidRandNodes(child,parent1);
		}
		//replace child sub tree with a CLONED sub tree of the other node
		if (nodes!=null){
			child.replaceSubTree(nodes[0],nodes[1].cloneSubTree(null));
			child.calcFitness();
		}
		return child;
	}

	/*chose 2 valid nodes to replace one with the other, 
	 *while maintaining max depth of tree and 
	 *same return value*/
	private Node[] choseValidRandNodes(Tree parent1, Tree parent2) {
		ArrayList<Integer> indexTypeOptions = createTypeOptions();
		Node nodes[] = choseRandNodes(parent1,parent2,indexTypeOptions );
		while(nodes!=null && parent1.getMaxDepth() - nodes[0].getDepth()<nodes[1].getSubNodeDepth()){
			if (indexTypeOptions.isEmpty()){
				nodes = null;
				break;
			}
			nodes = choseRandNodes(parent1,parent2, indexTypeOptions);
		}

		return nodes;
	}


	private ArrayList<Integer> createTypeOptions() {
		ArrayList<Integer> indexTypeOptions = new ArrayList<Integer>();
		indexTypeOptions.add(0);
		indexTypeOptions.add(1);
		indexTypeOptions.add(2);
		indexTypeOptions.add(3);
		return indexTypeOptions;
	}


	private Node[] choseRandNodes(Tree parent1, Tree parent2,ArrayList<Integer> indexTypeOptions) {
		int indexType = randIndexType(parent1,parent2, indexTypeOptions);
		if (indexType==-1){
			return null;
		}
		Node chosenNode = getRandNodeFromTree(indexType,parent1);
		Node secondChosenNode = getRandNodeFromTree(indexType,parent2);
		return new Node[]{chosenNode,secondChosenNode};
	}


	private Node getRandNodeFromTree(int indexType,Tree parent) {
		ArrayList<Node> listByType = parent.getAllNodesByType(indexType);
		Node ans=null;
		do{
			try{
				ans = listByType.get(random.nextInt(listByType.size()));
			}catch (IllegalArgumentException e){
				//System.out.println(parent);
				//System.out.println("indexType" + indexType);
			}
		}while (ans.getParent()==null);
		return ans;
	}

	private int randIndexType(Tree tree1,Tree tree2,ArrayList<Integer> indexTypeOptions) {
		int indexType =  indexTypeOptions.get(random.nextInt(indexTypeOptions.size()));
		while (tree1.getAllNodesByType(indexType).isEmpty() || 
			   tree2.getAllNodesByType(indexType).isEmpty() || 
			   isOnlyHead(indexType,tree1) || isOnlyHead(indexType,tree2)){
			indexTypeOptions.remove(new Integer(indexType));
			if (indexTypeOptions.isEmpty()){
				indexType = -1;
				break;
			}
			indexType =  indexTypeOptions.get(random.nextInt(indexTypeOptions.size()));
		}
		return indexType;
	}


	private boolean isOnlyHead(int indexType, Tree tree) {
		if (tree.getAllNodesByType(indexType).size()==1 && 
			tree.getAllNodesByType(indexType).get(0).getParent()==null){
			return true;
		}
		return false;
	}
}
